/*
 * Copyright 2019 Flipkart Internet Pvt. Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.flipkart.ohos.proteus.parser.custom;

import ohos.agp.components.Component;
import ohos.agp.components.ComponentContainer;

import com.flipkart.ohos.proteus.DataContext;
import com.flipkart.ohos.proteus.ProteusConstants;
import com.flipkart.ohos.proteus.ProteusContext;
import com.flipkart.ohos.proteus.ProteusLayoutInflater;
import com.flipkart.ohos.proteus.ProteusView;
import com.flipkart.ohos.proteus.ViewTypeParser;
import com.flipkart.ohos.proteus.exceptions.ProteusInflateException;
import com.flipkart.ohos.proteus.managers.ViewGroupManager;
import com.flipkart.ohos.proteus.processor.AttributeProcessor;
import com.flipkart.ohos.proteus.processor.BooleanAttributeProcessor;
import com.flipkart.ohos.proteus.processor.StringAttributeProcessor;
import com.flipkart.ohos.proteus.toolbox.Attributes;
import com.flipkart.ohos.proteus.value.AttributeResource;
import com.flipkart.ohos.proteus.value.Binding;
import com.flipkart.ohos.proteus.value.Layout;
import com.flipkart.ohos.proteus.value.NestedBinding;
import com.flipkart.ohos.proteus.value.ObjectValue;
import com.flipkart.ohos.proteus.value.Resource;
import com.flipkart.ohos.proteus.value.StyleResource;
import com.flipkart.ohos.proteus.value.Value;
import com.flipkart.ohos.proteus.view.ProteusAspectRatioStackLayout;

import java.util.Iterator;

public class ComponentContainerParser<T extends ComponentContainer> extends ViewTypeParser<T> {
    private static final String LAYOUT_MODE_CLIP_BOUNDS = "clipBounds";
    private static final String LAYOUT_MODE_OPTICAL_BOUNDS = "opticalBounds";

    @Override
    public String getType() {
        return "ComponentContainer";
    }

    @Override
    public String getParentType() {
        return "Component";
    }

    @Override
    public ProteusView createView(ProteusContext context, Layout layout, ObjectValue data,
                                  ComponentContainer parent, int dataIndex) {
        return new ProteusAspectRatioStackLayout(context);
    }

    @Override
    public ProteusView.Manager createViewManager(ProteusContext context, ProteusView view, Layout layout,
                                                 ObjectValue data, ViewTypeParser caller, ComponentContainer parent,
                                                 int dataIndex) {
        DataContext dataContext = createDataContext(context, layout, data, parent, dataIndex);
        return new ViewGroupManager(context, null != caller ? caller : this, view.getAsView(), layout, dataContext);
    }

    @Override
    protected void addAttributeProcessors() {
        addAttributeProcessor(Attributes.ComponentContainer.ClipChildren, new BooleanAttributeProcessor<T>() {
            @Override
            public void setBoolean(T view, boolean value) {
                view.setClipEnabled(value);
            }
        });

        addAttributeProcessor(Attributes.ComponentContainer.ClipToPadding, new BooleanAttributeProcessor<T>() {
            @Override
            public void setBoolean(T view, boolean value) {
                //view.setClipToPadding(value);
            }
        });

        addAttributeProcessor(Attributes.ComponentContainer.LayoutMode, new StringAttributeProcessor<T>() {
            @Override
            public void setString(T view, String value) {
                //if (LAYOUT_MODE_CLIP_BOUNDS.equals(value)) {
                //view.setLayoutMode(ComponentContainer.LAYOUT_MODE_CLIP_BOUNDS);
                //} else if (LAYOUT_MODE_OPTICAL_BOUNDS.equals(value)) {
                //view.setLayoutMode(ComponentContainer.LAYOUT_MODE_OPTICAL_BOUNDS);
                //}
            }
        });

        addAttributeProcessor(Attributes.ComponentContainer.SplitMotionEvents, new BooleanAttributeProcessor<T>() {
            @Override
            public void setBoolean(T view, boolean value) {
                //view.setMotionEventSplittingEnabled(value);
            }
        });

        addAttributeProcessor(Attributes.ComponentContainer.Children, new AttributeProcessor<T>() {
            @Override
            public void handleBinding(T view, Binding value) {
                handleDataBoundChildren(view, value);
            }

            @Override
            public void handleValue(T view, Value value) {
                handleChildren(view, value);
            }

            @Override
            public void handleResource(T view, Resource resource) {
                throw new IllegalArgumentException("children cannot be a resource");
            }

            @Override
            public void handleAttributeResource(T view, AttributeResource attribute) {
                throw new IllegalArgumentException("children cannot be a resource");
            }

            @Override
            public void handleStyleResource(T view, StyleResource style) {
                throw new IllegalArgumentException("children cannot be a style attribute");
            }
        });
    }

    @Override
    public boolean handleChildren(T view, Value children) {
        ProteusView proteusView = (ProteusView) view;
        ProteusView.Manager viewManager = proteusView.getViewManager();
        ProteusLayoutInflater layoutInflater = viewManager.getContext().getInflater();
        ObjectValue data = viewManager.getDataContext().getData();
        int dataIndex = viewManager.getDataContext().getIndex();
        if (children.isArray()) {
            ProteusView child;
            Iterator<Value> iterator = children.getAsArray().iterator();
            Value element;
            while (iterator.hasNext()) {
                element = iterator.next();
                if (!element.isLayout()) {
                    try {
                        throw new ProteusInflateException("attribute  'children' must be an array of 'Layout' objects");
                    } catch (ProteusInflateException e) {
                        e.printStackTrace();
                    }
                }
                child = layoutInflater.inflate(element.getAsLayout(), data, view, dataIndex);
                addView(proteusView, child);
            }
        }
        return true;
    }

    protected void handleDataBoundChildren(T view, Binding value) {
        ProteusView parent = (ProteusView) view;
        ViewGroupManager manager = (ViewGroupManager) parent.getViewManager();
        DataContext dataContext = manager.getDataContext();
        ObjectValue config = ((NestedBinding) value).getValue().getAsObject();
        Binding collection = config.getAsBinding(ProteusConstants.COLLECTION);
        Layout layout = config.getAsLayout(ProteusConstants.LAYOUT);
        manager.hasDataBoundChildren = true;
        if (layout == null || collection == null) {
            try {
                throw new ProteusInflateException("'collection' and 'layout' are mandatory for attribute:'children'");
            } catch (ProteusInflateException e) {
                e.printStackTrace();
            }
        }

        Value dataset = collection.getAsBinding().evaluate(view.getContext(),
            dataContext.getData(), dataContext.getIndex());
        if (dataset.isNull()) {
            return;
        }

        if (!dataset.isArray()) {
            try {
                throw new ProteusInflateException("'collection' in attribute:'children' must be NULL or Array");
            } catch (ProteusInflateException e) {
                e.printStackTrace();
            }
        }

        int length = dataset.getAsArray().size();
        int count = view.getChildCount();
        ObjectValue data = dataContext.getData();
        ProteusLayoutInflater inflater = manager.getContext().getInflater();
        ProteusView child;
        Component temp;

        if (count > length) {
            while (count > length) {
                count--;
                view.removeComponentAt(count);
            }
        }

        for (int index = 0; index < length; index++) {
            if (index < count) {
                temp = view.getComponentAt(index);
                if (temp instanceof ProteusView) {
                    ((ProteusView) temp).getViewManager().update(data);
                }
            } else {
                //noinspection ConstantConditions : We want to throw an exception if the layout is null
                child = inflater.inflate(layout, data, view, index);
                addView(parent, child);
            }
        }
    }

    @Override
    public boolean addView(ProteusView parent, ProteusView view) {
        if (parent instanceof ComponentContainer) {
            ((ComponentContainer) parent).addComponent(view.getAsView());
            return true;
        }
        return false;
    }
}
